前面幾天談的都是純文字的資料驗證,像是信箱、電話等等,但很多 API server 除了文字資料外也會提供上傳檔案、照片的功能,尤其現在那麼多電商一定會需要使用者上傳大頭貼、商品照之類的,有時甚至還會上傳影片,所以今天要來說說對於使用者上傳的檔案有哪些應該做的檢查
雖然這個系列是以後端的 API Server 為主,不過這邊也順便講一下在前端怎麼限制檔案類型:平常在前端實作上傳檔案時一定會有一個 <input type="file">
的 HTML 元素,這樣使用者才能按下按鈕選擇檔案。
而從 HTML5 開始,input 元件多了一個 accept 屬性可以設定,所以你可以透過 <input type="file" accept=".png,.jpg">
來設定副檔名,或是直接用 <input type="file" accept="image/*,video/*">
這種 MIME-Type 的格式來接受所有的圖片、影片檔,那使用者在選擇時就會像下圖這樣不會選錯檔案
但因為這只是前端的限制,只要 API 在那邊,攻擊者一定有辦法直接從瀏覽器以外的地方上傳檔案,所以馬上來說說後端對於使用者傳上來的檔案該做哪些檢查
先舉個 Node.js 的例子,如果你的 API server 是用 multer 在處理檔案上傳的話,那可以透過 fileFilter
來設定要接受哪些類型的檔案,譬如說當 file.mimetype
是 jpg 或 jpeg 才接受,其他的檔案類型則拒絕上傳
var upload = multer({
storage: storage,
fileFilter: (req, file, cb) => {
if (file.mimetype == "image/jpg" || file.mimetype == "image/jpeg") {
cb(null, true) // 接受這個檔案
} else {
cb(null, false) // 拒絕這個檔案
return cb(new Error('Only .jpg/.jpeg format allowed!'))
}
}
})
app.post('/avatar', upload.single('avatar'), function (req, res) {
// save image here
});
而在 Gin(Go 的框架)裡面做起來也滿簡單的,只要先從 *gin.Context
裡面拿到檔案,再判斷 MIME Type 是不是允許的類型就可以了~如果不是的話就直接回一個 400 給他
import (
"github.com/gabriel-vasile/mimetype"
"github.com/gin-gonic/gin"
)
func handler(ctx *gin.Context) error {
fileHeader, err := ctx.FormFile("file")
file, err := fFile.Open()
mimeType, err := mimetype.DetectReader(io.Reader) // 偵測 MIME type
// 把不要的格式拒絕掉
if mimeType != "image/png" {
c.JSON(400, gin.H{"error": "Only .png format allowed!"})
}
}
除了檔案類型之外,限制檔案大小也非常重要,如果完全不做限制,那攻擊者只要同時上傳一堆很大的檔案,譬如說一次塞給你 10GB,那整個伺服器就會變得非常慢,記憶體也有可能因此被塞爆
因為這個功能非常常見,所以 Node.js 的 multer 也實作了一個 limits
屬性讓你做設定,如果你想限制上傳的大頭貼最大 10MB,那就設定 10 << 20(也就是 10 * 1024 * 1024 bytes)就可以了~
var upload = multer({
storage: storage,
fileFilter: (req, file, cb) => { /* ... */ },
limits: { fileSize: 10 << 20 } // 在這裡設定 max size = 10MB
});
而 Go 的話也很簡單,只要在進到 handler 之前先過一個 size limiter 就可以了,這樣就能確保上傳的檔案都不會太大
import "github.com/gin-contrib/size"
func handler(ctx *gin.Context) {
// 先檢查剛剛有沒有發生錯誤
if len(ctx.Errors) > 0 {
return
}
// 都沒問題再把檔案存起來
}
func main() {
router := gin.Default()
// 先檢查 request size 再進到 handler
router.POST("/avatar", limits.RequestSizeLimiter(10 << 20), handler)
router.Run(":8888")
}
今天介紹了為什麼 API Server 在收到檔案時要檢查 MIME Type 跟檔案大小,也示範了在 Node.js 跟 Go 裡面怎麼用現有的 library 來實作
關於「入口管制」的部分就到今天結束,如果對於內容有什麼問題的話歡迎在下方留言,沒有的話明天就要開始「流量限制」的部分囉~